package com.google.code.mergeinformer.config;

import com.google.code.mergeinformer.model.Language;
import com.google.code.mergeinformer.util.AppUtils;
import com.google.code.mergeinformer.util.SecurityUtils;
import freemarker.cache.ClassTemplateLoader;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.TemplateModelException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;
import org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean;
import org.springframework.validation.Validator;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

/**
 * @author Dmitry Golubev
 */
@Configuration
@EnableWebMvc
@ComponentScan({
        "com.google.code.mergeinformer.controller",
        "com.google.code.mergeinformer.interceptor"
})
public class WebConfig extends WebMvcConfigurerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(WebConfig.class);

    @Autowired
    private ServicesConfig servicesConfig;

    @Autowired
    private HandlerInterceptor templateModelInterceptor;

    private static final String LOCALE_COOKIE_NAME = "mergeinformer.locale.key";

    @Bean
    public FreeMarkerConfigurationFactoryBean freeMarkerConfigurationFactoryBean() {
        final FreeMarkerConfigurationFactoryBean bean = new FreeMarkerConfigurationFactoryBean();
        bean.setDefaultEncoding("UTF-8");
        bean.setTemplateLoaderPath("/WEB-INF/views/");
        // add loader to load spring.ftl
        bean.setPostTemplateLoaders(new ClassTemplateLoader(FreeMarkerConfigurer.class, ""));
        return bean;
    }

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() throws TemplateModelException {
        final FreeMarkerConfigurer bean = new FreeMarkerConfigurer();

        final freemarker.template.Configuration configuration = freeMarkerConfigurationFactoryBean().getObject();

        configuration.setURLEscapingCharset("UTF-8");
        configuration.setDateTimeFormat("dd.MM.yy HH:mm:ss");
        configuration.setDateFormat("dd.MM.yy");
        configuration.setTimeFormat("HH:mm:ss");
        configuration.setNumberFormat("0.##");
        configuration.setBooleanFormat("true,false");

        configuration.addAutoImport("spring", "spring.ftl");
        configuration.addAutoImport("app", "macro/app.ftl");
        configuration.addAutoImport("layout", "macro/layout.ftl");

        configuration.addAutoInclude("macro/autoInclude.ftl");

        // use default update delay for dev (5 seconds)
        if (AppUtils.isProd()) {
            // store templates cache as long as possible
            configuration.setTemplateUpdateDelay(Integer.MAX_VALUE);
            // rethrow errors, do not show ugly stacktraces in browser
            configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        }

        putSharedVariables(configuration);

        bean.setConfiguration(configuration);

        return bean;
    }

    private void putSharedVariables(freemarker.template.Configuration configuration) {
        try {
            configuration.setSharedVariable("jsRevision", AppUtils.getProperty("app.resources.js.committedRevision"));
            configuration.setSharedVariable("cssRevision", AppUtils.getProperty("app.resources.css.committedRevision"));
            configuration.setSharedVariable("imgRevision", AppUtils.getProperty("app.resources.img.committedRevision"));
            configuration.setSharedVariable("msgRevision", AppUtils.getProperty("app.resources.msg.committedRevision"));

            configuration.setSharedVariable("buildInfo", AppUtils.getProperty("app.name")
                    + " version:" + AppUtils.getProperty("app.version")
                    + " revision:" + AppUtils.getProperty("app.revision")
                    + " profile:" + AppUtils.getProperty("app.profile")
                    + " environment:" + AppUtils.getEnvironment().getValue());

            configuration.setSharedVariable("revisionViewer", AppUtils.getProperty("app.svn.revision.viewerUrl"));
            configuration.setSharedVariable("issueViewer", AppUtils.getProperty("app.viewIssue.url"));
            configuration.setSharedVariable("issuePattern", AppUtils.getProperty("app.viewIssue.issuePattern"));
        } catch (TemplateModelException e) {
            throw new RuntimeException(e);
        }
    }

    @Bean
    public FreeMarkerViewResolver freeMarkerViewResolver() {
        final FreeMarkerViewResolver bean = new FreeMarkerViewResolver();
        bean.setCache(AppUtils.isProd());
        bean.setSuffix(".ftl");
        bean.setContentType("text/html; charset=UTF-8");
        bean.setExposeSpringMacroHelpers(true);
        bean.setExposeRequestAttributes(true);
        bean.setExposeSessionAttributes(true);
        bean.setExposePathVariables(true);
        return bean;
    }

    @Bean
    public LocaleResolver localeResolver() {
        return new LocaleResolver() {

            private final CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
            {
                cookieLocaleResolver.setCookieName(LOCALE_COOKIE_NAME);
                cookieLocaleResolver.setDefaultLocale(Language.EN.getLocale());
            }

            @Override
            public Locale resolveLocale(HttpServletRequest request) {
                if (SecurityUtils.isAuthenticated()) {
                    // use user's settings for locale resolving
                    return SecurityUtils.getCurrentUser().getLanguage().getLocale();
                } else {
                    return cookieLocaleResolver.resolveLocale(request);
                }
            }

            @Override
            public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
                if (!SecurityUtils.isAuthenticated()) {
                    // store locale using cookie only for not authenticated users
                    cookieLocaleResolver.setLocale(request, response, locale);
                }
            }
        };
    }

    @Bean
    public ReloadableResourceBundleMessageSource messageSource() {
        final ReloadableResourceBundleMessageSource bean = new ReloadableResourceBundleMessageSource();
        bean.setBasenames("/resources/msg/messages");
        if (AppUtils.isDev()) {
            // disable caching to allow resource reloading on running application server
            bean.setCacheSeconds(0);
        } else {
            bean.setCacheSeconds(AppUtils.getPropertyInt("app.resources.cacheSeconds"));
        }
        bean.setDefaultEncoding("UTF-8");
        return bean;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LocaleChangeInterceptor());

        final OpenEntityManagerInViewInterceptor openInView = new OpenEntityManagerInViewInterceptor();
        openInView.setEntityManagerFactory(servicesConfig.entityManagerFactory().getObject());
        registry.addWebRequestInterceptor(openInView);

        registry.addInterceptor(templateModelInterceptor);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        final ResourceHandlerRegistration resources = registry.addResourceHandler("/resources/**")
                .addResourceLocations("/resources/");
        if (AppUtils.isDev()) {
            resources.setCachePeriod(0);
        } else {
            resources.setCachePeriod(AppUtils.getPropertyInt("app.resources.cacheSeconds"));
        }
    }

    @Override
    public Validator getValidator() {
        return servicesConfig.validator();
    }
}
